package com.onluy.frameworkcloud.batis.tools.util;


import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import com.onluy.frameworkcloud.batis.bean.EntityBoundSql;
import com.onluy.frameworkcloud.batis.bean.OperColumnBean;
import com.onluy.frameworkcloud.batis.dialect.Dialect;
import com.onluy.frameworkcloud.batis.exception.NoUpdateColumnException;
import com.onluy.frameworkcloud.data.annotation.Table;
import com.onluy.frameworkcloud.data.entity.Condition;
import com.onluy.frameworkcloud.data.entity.Conditions;
import com.onluy.frameworkcloud.data.entity.Entity;
import com.onluy.frameworkcloud.data.entity.EntityHelper;
import com.onluy.frameworkcloud.data.entity.OperColumn;
import com.onluy.frameworkcloud.data.entity.OperColumnHelper;
import com.onluy.frameworkcloud.data.enums.SQLSymbol;
import com.onluy.frameworkcloud.data.exception.ConditionEmptyException;
import com.onluy.frameworkcloud.data.exception.ConditionValueIsNullException;
import com.onluy.frameworkcloud.data.exception.EmptyEntityException;

public class SQlGenerator
{
    public final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss";
    private Dialect dialect;

    public SQlGenerator(Dialect dialect)
    {
        this.dialect = dialect;
    }

    public EntityBoundSql generateInsertSql(Configuration configuration, Entity entity)
    {
        Map operColumns = getOperColumns(entity, false).getOperColumns();
        List parameterMappings = new ArrayList();

        StringBuilder sql1 = new StringBuilder();
        StringBuilder sql2 = new StringBuilder();
        sql1.append(new StringBuilder().append("INSERT INTO ").append(getTableName(entity)).append(" (").toString());
        sql2.append(") VALUES (");

        for ( Object   entrytemp : operColumns.entrySet()) {
            Entry entry=(Entry)entrytemp;
            String column = (String)entry.getKey();
            OperColumn operColumn = (OperColumn)entry.getValue();
            if (OperColumnHelper.getOperColumnValue(operColumn) != null) {
                sql1.append(column).append(",");
                sql2.append("?").append(",");

                ParameterUtil.addParameterMapping(configuration, parameterMappings, OperColumnHelper.getOperColumnProperty(operColumn), OperColumnHelper.getOperColumnType(operColumn));
            }
        }
        sql1.deleteCharAt(sql1.length() - 1);
        sql2.deleteCharAt(sql2.length() - 1);
        sql1.append(sql2).append(")");
        return new EntityBoundSql(sql1.toString(), parameterMappings);
    }

    public EntityBoundSql generateUpdateSql(Configuration configuration, Entity entity)
    {
        Map operColumns = getOperColumns(entity, true).getOperColumns();
        List parameterMappings = new ArrayList();

        StringBuilder sql1 = new StringBuilder();
        StringBuilder sql2 = new StringBuilder();
        sql1.append(new StringBuilder().append("UPDATE ").append(getTableName(entity)).append(" SET ").toString());
        sql2.append(" WHERE 1 = 1");

        int updateColumn = 0;
        for ( Object   entrytemp : operColumns.entrySet()) {
            Entry entry=(Entry)entrytemp;
            String column = (String)entry.getKey();
            OperColumn operColumn = (OperColumn)entry.getValue();
            if (!OperColumnHelper.isCondition(operColumn)) {
                if (OperColumnHelper.getOperColumnValue(operColumn) != null) {
                    sql1.append(column).append(" = ?,");

                    ParameterUtil.addParameterMapping(configuration, parameterMappings, OperColumnHelper.getOperColumnProperty(operColumn), OperColumnHelper.getOperColumnType(operColumn));
                } else {
                    sql1.append(column).append(" = NULL,");
                }
                updateColumn++;
            }
        }
        if (updateColumn == 0) {
            throw new NoUpdateColumnException();
        }

        for ( Object   entrytemp : operColumns.entrySet()) {
            Entry entry=(Entry)entrytemp;
            OperColumn operColumn = (OperColumn)entry.getValue();
            if (OperColumnHelper.isCondition(operColumn)) {
                conditionHandler(sql2, operColumn, configuration, parameterMappings);
            }
        }
        sql1.deleteCharAt(sql1.length() - 1);
        sql1.append(sql2);
        return new EntityBoundSql(sql1.toString(), parameterMappings);
    }

    public EntityBoundSql generateDeleteSql(Configuration configuration, Entity entity)
    {
        OperColumnBean operColumnBean = getOperColumns(entity, true);
        Map operColumns = operColumnBean.getOperColumns();
        List parameterMappings = new ArrayList();

        StringBuilder sql = new StringBuilder();
        sql.append(new StringBuilder().append("DELETE FROM ").append(getTableName(entity)).toString());
        sql.append(" WHERE 1 = 1");
        for ( Object   entrytemp : operColumns.entrySet()) {
            Entry entry=(Entry)entrytemp;
            OperColumn operColumn = (OperColumn)entry.getValue();

            if (OperColumnHelper.isCondition(operColumn)) {
                conditionHandler(sql, operColumn, configuration, parameterMappings);
            }
        }
        return new EntityBoundSql(sql.toString(), parameterMappings);
    }

    public EntityBoundSql generateSelectSql(Configuration configuration, Entity entity)
    {
        OperColumnBean operColumnBean = getOperColumns(entity, false);
        Map operColumns = operColumnBean.getOperColumns();
        List parameterMappings = new ArrayList();

        StringBuilder sql1 = new StringBuilder();
        StringBuilder sql2 = new StringBuilder();
        sql1.append("SELECT *");
        sql1.append(new StringBuilder().append(" FROM ").append(getTableName(entity)).toString());
        sql1.append(" WHERE 1 = 1");

        sql2.append(" ORDER BY ");

        boolean hasOrderBy = false;
        if (operColumns != null) {
            for ( Object   entrytemp : operColumns.entrySet()) {
                Entry entry=(Entry)entrytemp;
                String column = (String)entry.getKey();
                OperColumn operColumn = (OperColumn)entry.getValue();

                if (OperColumnHelper.isCondition(operColumn)) {
                    conditionHandler(sql1, operColumn, configuration, parameterMappings);
                }

                String sort = OperColumnHelper.getSort(operColumn);
                if (sort != null) {
                    sql2.append(column).append(" ").append(sort).append(",");
                    hasOrderBy = true;
                }
            }
        }

        if (hasOrderBy) {
            sql2.deleteCharAt(sql2.length() - 1);
            sql1.append(sql2);
        }
        return new EntityBoundSql(sql1.toString(), parameterMappings);
    }

    private OperColumnBean getOperColumns(Entity entity, boolean assertCondition)
    {
        Map operColumns = null;
        if (entity == null) {
            throw new EmptyEntityException();
        }
        operColumns = EntityHelper.operColumns(entity);
        if (((operColumns == null) || (operColumns.keySet().size() == 0)) &&
                (assertCondition)) {
            throw new EmptyEntityException();
        }

        boolean hasCondition = false;
        if (operColumns != null) {
            for ( Object   entrytemp : operColumns.entrySet()) {
                Entry entry=(Entry)entrytemp;
                if (OperColumnHelper.isCondition((OperColumn)entry.getValue())) {
                    hasCondition = true;
                    break;
                }
            }
        }
        if ((assertCondition) && (!hasCondition)) {
            throw new ConditionEmptyException();
        }
        return new OperColumnBean(operColumns, hasCondition);
    }

    public String getTableName(Entity entity)
    {
        if (Conditions.class.isAssignableFrom(entity.getClass())) {
            return ((Table)EntityUtil.findEntity(EntityHelper.getEntityClazz((Conditions)entity)).getAnnotation(Table.class)).name();
        }
        return ((Table)EntityUtil.findEntity(entity.getClass()).getAnnotation(Table.class)).name();
    }

    private void conditionHandler(StringBuilder sql, OperColumn operColumn, Configuration configuration, List<ParameterMapping> parameterMappings)
    {
        Condition condition = OperColumnHelper.getCondition(operColumn);
        sql.append(" AND ").append(condition.getColumn());

        String symbol = condition.getSymbol();
        if ((SQLSymbol.IS_NULL.value.equals(symbol)) || (SQLSymbol.NOT_NULL.value.equals(symbol))) {
            sql.append(symbol);
        }
        else if (condition.isPre()) {
            if (OperColumnHelper.getOperColumnValue(operColumn) == null)
            {
                sql.append(SQLSymbol.IS_NULL.value);
            } else {
                sql.append(symbol).append("?");

                ParameterUtil.addParameterMapping(configuration, parameterMappings, OperColumnHelper.getOperColumnProperty(operColumn), OperColumnHelper.getOperColumnType(operColumn));
            }
        }
        else if ((SQLSymbol.IN.value.equals(symbol)) || (SQLSymbol.NOT_IN.value.equals(symbol))) {
            sql.append(symbol).append("(").append(inValueHandler(condition.getColumn(), condition.getValues())).append(")");
        } else if ((SQLSymbol.BETWEEN.value.equals(symbol)) || (SQLSymbol.NOT_BETWEEN.value.equals(symbol))) {
            String[] retValues = betweenValueHandler(condition.getColumn(), condition.getValues());
            sql.append(symbol).append(retValues[0]).append(" AND ").append(retValues[1]);
        } else {
            Object[] conValues = condition.getValues();
            if ((conValues == null) || (conValues.length == 0)) {
                throw new ConditionValueIsNullException(condition.getColumn());
            }
            sql.append(symbol).append(valueHandler(condition.getColumn(), condition.getValues()[0]));
        }
    }

    private String[] betweenValueHandler(String condition, Object[] values)
    {
        if ((values == null) || (values.length != 2)) {
            throw new ConditionValueIsNullException(condition);
        }
        String[] retValues = new String[2];
        for (int i = 0; i < values.length; i++) {
            retValues[i] = valueHandler(condition, values[i]);
        }
        return retValues;
    }

    private String inValueHandler(String condition, Object[] values)
    {
        if ((values == null) || (values.length == 0)) {
            throw new ConditionValueIsNullException(condition);
        }
        StringBuilder inValues = new StringBuilder();
        for (int i = 0; i < values.length; i++) {
            inValues.append(valueHandler(condition, values[i])).append(",");
        }
        if (inValues.length() > 1) {
            inValues.deleteCharAt(inValues.length() - 1);
        }
        return inValues.toString();
    }

    private String valueHandler(String condition, Object value)
    {
        String retValue = "";
        if (value == null) {
            throw new ConditionValueIsNullException(condition);
        }

        if ((value instanceof Date))
            retValue = this.dialect.getToDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(value));
        else {
            retValue = new StringBuilder().append("'").append(value.toString()).append("'").toString();
        }
        return retValue;
    }
}